import 'dart:convert';
import 'dart:math';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:hand_signature/signature.dart';

import 'hanzi_transform.dart';

/// 绘制字体时圆基准半径
const _basisRadio = 90.0;

enum CopybookStrokeResult{
  //成功
  success,
  // 画偏了
  deviation,
  // 不完整
  notIntact,
  // 第一个点未击中
  firstNotHit,
  // 最后一个点未击中
  lastNotHit,
  // 反着画
  reverse,
}

///练字字帖
class HanziCopybookWidget extends StatefulWidget {
  ///每一笔结果回调
  final Function(int strokeIndex, CopybookStrokeResult result)? onStrokeResult;
  ///全部完成结果回调
  final VoidCallback? onComplete;
  ///汉字数据
  final String chineseData;

  ///汉字背景颜色 不设置不显示背景
  final Color backgroundColor;
  ///汉字颜色
  final Color color;
  ///写失败额外
  final Color failColor;
  final int strokeIndex;

  const HanziCopybookWidget({
    super.key,
    required this.chineseData,
    this.backgroundColor = const Color(0xFFECEFF1),
    this.color = Colors.black,
    this.failColor = const Color(0xFFE57373),
    this.strokeIndex = 0,
    this.onStrokeResult,
    this.onComplete,
  });

  @override
  State<HanziCopybookWidget> createState() => _HanziCopybookWidgetState();
}

class _HanziCopybookWidgetState extends State<HanziCopybookWidget> with SingleTickerProviderStateMixin {
  HandSignatureControl controller = HandSignatureControl(
    threshold: 2,
    smoothRatio: 0.5,
    velocityRange: 1.0,
  );
  late AnimationController aniController;
  late Animation<double> _animation;
  int mStrokeIndex = 0;
  late Map<dynamic, dynamic> _chineseJson;
  late List<Path> _strokePathList;
  late List<PathMetric> _metricList;
  int _aniCount = 0;
  double _scale = 1;
  @override
  void initState() {

    _chineseJson = jsonDecode(widget.chineseData);
    mStrokeIndex = widget.strokeIndex;
    if (mStrokeIndex < 0) {
      mStrokeIndex = 0;
    } else if (mStrokeIndex > _chineseJson['strokes'].length - 1) {
      mStrokeIndex = _chineseJson['strokes'].length - 1;
    }
    super.initState();

    aniController = AnimationController(vsync: this, duration: const Duration(milliseconds: 100));
    _animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: aniController, curve: Curves.linear));
    aniController.addStatusListener((status) {
      if (status == AnimationStatus.completed){
        _aniCount ++;
        aniController.reverse(from: 1);
      } else if (status == AnimationStatus.dismissed){
        if (_aniCount > 3){
          return;
        }
        _aniCount ++;
        aniController.forward(from: 0);
      }
    });
  }

  playFailAni() {
    _aniCount = 0;
    aniController.forward(from: 0);
  }

  ///检查笔划是否合格
  void checkStroke(CubicPath path) {
    if (mStrokeIndex >= _metricList.length){
      return;
    }
    ///没有偏离, 是否完整, 第一个点是否命中, 最后一个点是否命中
    bool isNotDeviation = false, isIntact = false, isFirstHit = false, isLastHit = false;
    ///命中数量
    int hitCount = 0;
    Offset? firstPoint;
    Offset? lastPoint;

    //1. 画的点命中数达到80%通关
    Path strokePath = Path();
    for (var o in path.points) {
      if (_strokePathList[mStrokeIndex].contains(o)){
        hitCount++;
      }
      strokePath.addRect(Rect.fromCenter(center: o, width: _basisRadio * _scale, height: _basisRadio * _scale));
    }
    if (hitCount / path.points.length > 0.8){
      isNotDeviation = true;
    }

    //2. 骨骼点命中数量达到80%即认为通关
    hitCount = 0;
    var metric = _metricList[mStrokeIndex];
    for (var i = 0; i < metric.length; ++i) {
      Tangent? t = metric.getTangentForOffset(i.toDouble());
      Offset point = t!.position;
      if (i == 0){
        firstPoint = point;
      } else if (i == metric.length.toInt() - 1){
        lastPoint = point;
      }
      if (strokePath.contains(point)){
        hitCount++;
      }
    }
    if (hitCount / metric.length > 0.8){
      isIntact = true;
    }

    // 3. 判断第一个点和最后一个点是否击中
    Path firstPath = Path()..addRect(Rect.fromCenter(center: firstPoint!, width: _basisRadio * _scale, height: _basisRadio * _scale));
    Path lastPath = Path()..addRect(Rect.fromCenter(center: lastPoint!, width: _basisRadio * _scale, height: _basisRadio * _scale));
    if (firstPath.contains(path.points.first)){
      isFirstHit = true;
    }
    if (lastPath.contains(path.points.last)){
      isLastHit = true;
    }

    //还需要笔顺判断吗?
    if (!isNotDeviation){
      debugPrint("画跑偏了");
      playFailAni();
      if (widget.onStrokeResult != null){
        widget.onStrokeResult!(mStrokeIndex, CopybookStrokeResult.deviation);
      }
      return;
    }
    if (!isIntact){
      debugPrint("不完整");
      playFailAni();
      if (widget.onStrokeResult != null){
        widget.onStrokeResult!(mStrokeIndex, CopybookStrokeResult.notIntact);
      }
      return;
    }
    if (isIntact && !isFirstHit && !isLastHit){
      debugPrint("可能是反着画的?");
      playFailAni();
      if (widget.onStrokeResult != null){
        widget.onStrokeResult!(mStrokeIndex, CopybookStrokeResult.reverse);
      }
      return;
    }
    if (!isFirstHit){
      debugPrint("开头画错");
      playFailAni();
      if (widget.onStrokeResult != null){
        widget.onStrokeResult!(mStrokeIndex, CopybookStrokeResult.firstNotHit);
      }
      return;
    }
    if (!isLastHit){
      debugPrint("结尾画错");
      playFailAni();
      if (widget.onStrokeResult != null){
        widget.onStrokeResult!(mStrokeIndex, CopybookStrokeResult.lastNotHit);
      }
      return;
    }
    setState(() {
      if (mStrokeIndex < _metricList.length){
        if (widget.onStrokeResult != null){
          widget.onStrokeResult!(mStrokeIndex, CopybookStrokeResult.success);
        }
        mStrokeIndex ++;
        if (mStrokeIndex == _metricList.length && widget.onComplete != null){
          widget.onComplete!();
        }
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {

        var json = jsonDecode(widget.chineseData);
        _scale = min(constraints.maxWidth, constraints.maxHeight) / 1024;
        HanziTransform.transformData(json, scale: _scale);
        _strokePathList = HanziTransform.transformStrokePathList(json);
        _metricList = HanziTransform.transformMetricList(json);
        return Stack(
          fit: StackFit.expand,
          children: [
            CustomPaint(
              painter: CopybookPainter(
                repaint: _animation,
                  strokePathList: _strokePathList,
                  metricList: _metricList,
                  scale: _scale,
                  backgroundColor: widget.backgroundColor,
                  failColor: widget.failColor,
                  color: widget.color, strokeIndex: mStrokeIndex),
            ),
            HandSignature(
              control: controller,
              type: SignatureDrawType.arc,
              maxWidth: _scale * 36,
              width: _scale * 10,
              onPointerUp: () {
                checkStroke(controller.paths.last);
                controller.paths.removeLast();
              },
            ),
          ],
        );
      },
    );
  }

  @override
  void dispose() {
    aniController.dispose();
    controller.dispose();
    super.dispose();
  }
}

class CopybookPainter extends CustomPainter {
  final Color color;
  final Color backgroundColor;
  final Color failColor;
  final Animation<double>? repaint;
  final List<PathMetric> metricList;
  final List<Path> strokePathList;
  final int strokeIndex;
  final double scale;

  CopybookPainter({
    this.color = Colors.black,
    this.backgroundColor = const Color(0xFFECEFF1),
    this.failColor = Colors.red,
    this.repaint,
    required this.metricList,
    required this.strokePathList,
    required this.strokeIndex,
    required this.scale,
  })  : strokePaint = Paint()..color = color,
        backgroundPaint = Paint()..color = backgroundColor,
        failPaint = Paint()..color = failColor,
        super(repaint: repaint);

  final Paint strokePaint;
  final Paint backgroundPaint;
  final Paint failPaint;

  @override
  void paint(Canvas canvas, Size size) {
    for (var i = 0; i < strokePathList.length; ++i) {
      Path path = strokePathList[i];
      canvas.drawPath(path, backgroundPaint);
    }
    for (var i = 0; i < strokePathList.length; ++i) {
      Path path = strokePathList[i];
      if (strokeIndex > i) {
        canvas.drawPath(path, strokePaint);
      } else if (strokeIndex == i){
        canvas.drawPath(path, failPaint..color = failColor.withOpacity(repaint?.value??0));
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}
